/*
* Copyright 2013 eBuddy B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ebuddy.cassandra.cql.dao;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotSame;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.cassandraunit.utils.EmbeddedCassandraServerHelper;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.ConsistencyLevel;
import com.datastax.driver.core.Query;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.InvalidQueryException;
import com.ebuddy.cassandra.BatchContext;
import com.ebuddy.cassandra.Path;
import com.ebuddy.cassandra.StructuredDataSupport;
import com.ebuddy.cassandra.TypeReference;
import com.ebuddy.cassandra.databind.CustomTypeResolverBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* System tests for CqlStructuredDataSupport.
*
* @author Eric Zoerner <a href="mailto:ezoerner@ebuddy.com">ezoerner@ebuddy.com</a>
*/
public class CqlStructuredDataSupportSystemTest {
private static final String LOCALHOST_IP = "localhost";
private static final String CASSANDRA_HOSTS_SYSTEM_PROPERTY = "cassandra.hosts";
private static final String TEST_KEYSPACE = "cqlstructureddatasupportsystemtest";
private Cluster cluster;
private final String tableName = "testpojo";
private StructuredDataSupport<UUID> daoSupport;
private Session session;
@Captor
private ArgumentCaptor<Query> queryCaptor;
@BeforeMethod(groups = {"system"})
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
EmbeddedCassandraServerHelper.startEmbeddedCassandra();
// default to using cassandra on localhost, but can be overridden with a system property
String cassandraHostsString = System.getProperty(CASSANDRA_HOSTS_SYSTEM_PROPERTY, LOCALHOST_IP);
String[] cassandraHosts = StringUtils.split(cassandraHostsString, ',');
Cluster.Builder clusterBuilder = Cluster.builder();
for (String host : cassandraHosts) {
clusterBuilder.addContactPoint(host);
}
cluster = clusterBuilder.withPort(9142).build();
dropAndCreateSchema();
// get new session using a default keyspace that we now know exists
session = cluster.connect(TEST_KEYSPACE);
session = spy(session);
daoSupport = new CqlStructuredDataSupport<UUID>(tableName, ConsistencyLevel.QUORUM, session);
}
@AfterMethod(alwaysRun = true)
public void tearDown() throws Exception {
cluster.shutdown();
}
@Test(groups = {"system"})
public void shouldWriteReadDeleteTestPojo() throws Exception {
TestPojo testObject = new TestPojo("v1", 42L, true, Arrays.asList("e1", "e2"));
UUID rowKey = UUID.randomUUID();
Path path = daoSupport.createPath("a","b","c");
TypeReference<TestPojo> typeReference = new TypeReference<TestPojo>() { };
daoSupport.writeToPath(rowKey, path, testObject);
TestPojo result = daoSupport.readFromPath(rowKey, path, typeReference);
assertNotSame(result, testObject);
assertEquals(result, testObject);
daoSupport.deletePath(rowKey, path);
TestPojo result2 = daoSupport.readFromPath(rowKey, path, typeReference);
assertNull(result2);
verifyConsistency(5);
}
@Test(groups = {"system"})
public void shouldWriteInBatch() throws Exception {
TestPojo testObject1 = new TestPojo("v1", 42L, true, Arrays.asList("e1", "e2"));
TestPojo testObject2 = new TestPojo("v2", 43L, false, Arrays.asList("e3", "e4"));
UUID rowKey1 = UUID.randomUUID();
UUID rowKey2 = UUID.randomUUID();
Path path = daoSupport.createPath("test");
TypeReference<TestPojo> typeReference = new TypeReference<TestPojo>() { };
BatchContext batchContext = daoSupport.beginBatch();
daoSupport.writeToPath(rowKey1, path, testObject1, batchContext);
daoSupport.writeToPath(rowKey2, path, testObject2, batchContext);
daoSupport.applyBatch(batchContext);
TestPojo result1 = daoSupport.readFromPath(rowKey1, path, typeReference);
assertNotSame(result1, testObject1);
assertEquals(result1, testObject1);
TestPojo result2 = daoSupport.readFromPath(rowKey2, path, typeReference);
assertNotSame(result2, testObject2);
assertEquals(result2, testObject2);
verifyConsistency(3);
}
@Test(groups = {"system"})
public void shouldShrinkList() throws Exception {
List<String> longList = Arrays.asList("1", "2", "3", "4", "5", "6");
UUID rowKey = UUID.randomUUID();
TypeReference<List<String>> typeReference = new TypeReference<List<String>>() { };
Path path = daoSupport.createPath("x");
daoSupport.writeToPath(rowKey, path, longList);
List<String> resultLongList = daoSupport.readFromPath(rowKey, path, typeReference);
assertNotSame(resultLongList, longList);
assertEquals(resultLongList, longList);
List<String> shortList = Arrays.asList("1", "2", "3");
daoSupport.writeToPath(rowKey, path, shortList);
List<String> resultShortList = daoSupport.readFromPath(rowKey, path, typeReference);
assertNotSame(resultShortList, shortList);
assertEquals(resultShortList, shortList);
verifyConsistency(4);
}
@Test(groups = {"system"})
public void shouldRemoveListCruftWhenDeleting() throws Exception {
List<String> longList = Arrays.asList("1", "2", "3", "4", "5", "6");
UUID rowKey = UUID.randomUUID();
TypeReference<List<String>> typeReference = new TypeReference<List<String>>() { };
Path path = daoSupport.createPath("x");
daoSupport.writeToPath(rowKey, path, longList);
List<String> resultLongList = daoSupport.readFromPath(rowKey, path, typeReference);
assertNotSame(resultLongList, longList);
assertEquals(resultLongList, longList);
List<String> shortList = Arrays.asList("1", "2", "3");
daoSupport.writeToPath(rowKey, path, shortList);
List<String> resultShortList = daoSupport.readFromPath(rowKey, path, typeReference);
assertNotSame(resultShortList, shortList);
assertEquals(resultShortList, shortList);
Path indexPath = daoSupport.createPath("x").withIndices(4);
String s = daoSupport.readFromPath(rowKey,indexPath, new TypeReference<String>() {});
assertEquals(s, "5"); // cruft
daoSupport.deletePath(rowKey, path);
s = daoSupport.readFromPath(rowKey,indexPath, new TypeReference<String>() {});
assertNull(s); // cruft gone
verifyConsistency(8);
}
@SuppressWarnings("unchecked")
@Test(groups = {"system"})
public void convertValueShouldRetainOrderingInMaps() throws Exception {
SortedMap<String,String> map = new TreeMap<String,String>();
map.put("b", "1");
map.put("a", "2");
ObjectMapper mapper = new ObjectMapper();
mapper.setDefaultTyping(new CustomTypeResolverBuilder());
Object converted = mapper.convertValue(map, Object.class);
assertTrue(converted instanceof LinkedHashMap);
// keys are sorted by Cassandra as UTF8
assertEquals(((Map<String,String>)converted).keySet().iterator().next(), "a");
}
private void dropAndCreateSchema() {
Session localSession = cluster.connect();
try {
dropKeyspaceIfExists(localSession);
localSession.execute("CREATE KEYSPACE " + TEST_KEYSPACE + " WITH replication " + "= {'class':'SimpleStrategy', " +
"'replication_factor':1};");
localSession.execute("CREATE TABLE " + TEST_KEYSPACE + "." + tableName + " (key uuid, column1 text, " +
"value text, PRIMARY KEY (key, column1));");
} finally {
localSession.shutdown();
}
}
private void dropKeyspaceIfExists(Session localSession) {
try {
localSession.execute("drop keyspace " + TEST_KEYSPACE);
} catch (InvalidQueryException ignored) {
// doesn't exist
}
}
private void verifyConsistency(int numberOfInvocations) {
verify(session, times(numberOfInvocations)).execute(queryCaptor.capture());
for (Query q : queryCaptor.getAllValues()) {
assertEquals(q.getConsistencyLevel(), ConsistencyLevel.QUORUM);
}
}
}